/*

* Copyright (c) 2007, National University of Singapore (NUS)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without 
* modification, are permitted provided that the following conditions 
* are met:
*
*   * Redistributions of source code must retain the above copyright notice, 
*     this list of conditions,the authors and the following disclaimer.
*   * Redistributions in binary form must reproduce the above copyright notice,
*     this list of conditions,the authors and the following disclaimer in
*     the documentation and/or other materials provided with the distribution.
*   * Neither the name of the university nor the names of its 
*     contributors may be used to endorse or promote products derived from 
*     this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
* POSSIBILITY OF SUCH DAMAGE.
* 
* Author: Wenwei Xue (dcsxw@nus.edu.sg)
* 
*/
package psg.servicemanager;

/**
 * This class simulates the context data services at a PSG.
 */
public class ContextDataServices {
	
	/**
	 * The set of context domains the physical space belongs to, the
	 * attributes provided in each domain and its current value.
	 */
	ContextDomain cd [] = null;
	
	EventServerSocket ess;	// the server socket for event subscription
	
	int eventID = 0;	// the unique identifier for an event subscription
	
	//public PhysicalSpaceGUI psg;	// gateway GUI for a simulated PSG
	
	public ContextDataServices (String domains [], String attrNames [][], int EventServerSocketPort) {
		
		int i,j;
		String attrs [];
		ContextAttribute ca;
		
		cd = new ContextDomain [domains.length];
		
		for (i=0;i<cd.length;i++)
		{
			cd[i] = new ContextDomain();
			
			cd[i].name = domains[i].toLowerCase();
			
			attrs = attrNames[i];
			
			cd[i].contextAttrs = new ContextAttribute [attrs.length];
			
			for (j=0;j<attrs.length;j++)
				{
					ca = new ContextAttribute();
					ca.name = attrs[j];
					ca.value = ContextAttribute.assignDefaultValue(ca.name);
					cd[i].contextAttrs[j] = ca;					
				}
		}
	 
	   ess = new EventServerSocket(this, EventServerSocketPort);	   	
	   ess.start();
	   
	}  // ContextDataServices
	
	/**
	 * This function closes the event server socket.
	 */
	public void close() {
		ess.isStopped = true;
	}
	
	/**
	 * This function examines whether a given name for a context domain that the physical space belongs to is valid.
	 * @param name a given name
	 * @return TRUE if the input is the valid name for a context domain of the physical space; otherwise FALSE
	 */
	public boolean checkDomain(String domain) {
		
		int i;
		domain = domain.toLowerCase();
		for (i=0;i<cd.length;i++)
			if (domain.equals(cd[i].name.toLowerCase()))
				return true;
		
		return false;	
		
	}	// checkDomain
	
	
	/**
	 * This function examines whether a given attribute is valid for a context domain that the physical space belongs to.
	 * @param domain name of a context domain
	 * @param attrName name of a context attribute in the domain
	 * @return TRUE if the attribute is valid for the domain; otherwise FALSE
	 */
	public boolean checkName(String domain, String name) {
		
		domain = domain.toLowerCase();
		name = name.toLowerCase();
		/*
		if (name.indexOf(ContextAttribute.eventNames[2].toLowerCase())== 0)
		{
			int start, end;
			
			start = ContextAttribute.eventNames[2].length();
			
			start = name.indexOf("(",start);
			
			if (start != -1)
			{
				start ++;
				
				end = name.indexOf(")",start);
				
				if (end != -1) 
					name = name.substring(start,end).trim();				
			}
		}
		*/
		int i,j;
		for (i=0;i<cd.length;i++)
			if (domain.equals(cd[i].name.toLowerCase()))
				break;
		if (i==cd.length)
			return false;
			
		ContextAttribute ca;
		for (j=0;j<cd[i].contextAttrs.length;j++)
		{
			ca = cd[i].contextAttrs[j];
			if (ca.name.toLowerCase().equals(name))
				return true;
		}		
		
		return false;
		
	}	// checkName
	
	/**
	 * This function assigns a value to a context attribute.
	 * @param domain context domain of the attribute
	 * @param name attribute name in the domain
	 * @param value value of the attribute to be assigned
	 * @return TRUE if the value assignment is successful; FALSE otherwise
	 */
	public boolean assign (String domain, String name, String value) {
		
		domain = domain.toLowerCase();
		name = name.toLowerCase();
		//value = value.toLowerCase();	
		
		int i,j, k;
		for (i=0;i<cd.length;i++)
			if (domain.equals(cd[i].name.toLowerCase()))
				break;
		if (i==cd.length)
		{
			System.err.println("Invalid name of context domain: " + domain);
			
			return false;
		}
		
		if (!ContextAttribute.checkValue(name, value))
		{
			System.err.println("Invalid value of context attribute " + name + " in domain: " + domain);
			
			return false;
		}
		
		ContextAttribute ca;
		for (j=0;j<cd[i].contextAttrs.length;j++)
		{
			ca = cd[i].contextAttrs[j];
			if (ca.name.toLowerCase().equals(name))
			{
				ca.value = value;
				//System.out.println("...Test:NUS Home PSG~psg.servicemanager~ContextDataService~(Row 197).....Ivan..."+j);
				
				for (k=0;k<ContextAttribute.eventNames.length;k++)
					if (name.equals(ContextAttribute.eventNames[k].toLowerCase()))
						break;
				if (k!=ContextAttribute.eventNames.length && value.toLowerCase().equals("true"))
					updateEventNotificationStatus(k);
				
				return true;
			}
		}		
		if (j==cd[i].contextAttrs.length)
		  System.err.println("Invalid name of context attribute " + name + " in domain " + domain);
		    	    
	    return false;
	    
	}   // assign 
	
	/**
	 * This function acquires the current value of a context attribute in a domain.
	 * @param domain context domain of the attribute
	 * @param name attribute name in the domain
	 * @return value of the attribute; NULL if the the input attribute or domain name is invalid
	 */
	public String getContext(String domain, String name) {
		
		domain = domain.toLowerCase();
		name = name.toLowerCase();
		
		int i,j;
		for (i=0;i<cd.length;i++)
			if (domain.equals(cd[i].name.toLowerCase()))
				break;
		if (i==cd.length)
		{
			System.err.println("Invalid name of context domain: " + domain);
			
			return null;
		}
		
		ContextAttribute ca;
		for (j=0;j<cd[i].contextAttrs.length;j++)
		{
			ca = cd[i].contextAttrs[j];
			if (ca.name.toLowerCase().equals(name))
				return ca.value;
		}		
	    System.err.println("Invalid name of context attribute " + name + " in domain " + domain);
		    	    
	    return null;
	    
	}  // getContext
	
	/**
	 * This function examines whether the condition "name op val" is satisfied. 
	 * @param domain context domain of the attribute
	 * @param name attribute name in the domain
	 * @param op a comparsion operator such as ">" and "<="
	 * @param val a constant value for the attribute
	 * @return TRUE if the condition is satisfied; otherwise FALSE
	 */
	public boolean filterContext(String domain, String name, String op, String val) {
		
		domain = domain.toLowerCase();
		name = name.toLowerCase();
		
		int i,j;
		for (i=0;i<cd.length;i++)
			if (domain.equals(cd[i].name.toLowerCase()))
				break;
		if (i==cd.length)
		{
			System.err.println("Invalid name of context domain: " + domain);
			
			return false;
		}
		
		if (!ContextAttribute.checkValue(name, val))
		{
			System.err.println("Invalid value of context attribute " + name + " in domain: " + domain);
			
			return false;
		}
		
		ContextAttribute ca = null;
		for (j=0;j<cd[i].contextAttrs.length;j++)
		{
			ca = cd[i].contextAttrs[j];
			if (ca.name.toLowerCase().equals(name))
				break;
		}		
		if (j==cd[i].contextAttrs.length)
		{
			System.err.println("Invalid name of context attribute " + name + " in domain " + domain);
		    	    
	    	return false;
		}
		
		int mark;
		if (op.equals(">"))
			mark = 0;
		else if (op.equals("<"))
			   mark = 1;
			 else if (op.equals(">="))
			 	    mark = 2;
			 	  else if (op.equals("<="))
			 	  	     mark = 3;
			 	  	   else if (op.equals("==") || op.equals("="))
			 	  	   	      mark = 4;
			 	  	   	    else if (op.equals("<>")||op.equals("!="))
			 	  	   	    	   mark = 5;
			 	  	   	    	 else
			 	  	   	    	 {
			 	  	   	    	 	System.err.println("Invalid comparsion operator: " + op);
			 	  	   	    	 	return false;
			 	  	   	    	 }
			 	  	   	    	 
		switch (mark) {
			case 0: if (ca.value.compareTo(val)>0) return true; break;
		    case 1: if (ca.value.compareTo(val)<0) return true; break;
		    case 2: if (ca.value.compareTo(val)>=0) return true; break;
		    case 3: if (ca.value.compareTo(val)<=0) return true; break;
		    case 4: if (ca.value.compareTo(val)==0) return true; break;
		    case 5: if (ca.value.compareTo(val)!=0) return true; break;
		    default: return false;
		}
		
		return false;
		
	}  // filterContext
	
	/**
	 * This function subscribes to a pre-defined event.
	 * @param domain context domain in which the event is defined 
	 * @param name name of an event
	 * @return socket address of a Sever Socket that the caller can connects 
	 *         to later if the input event name if valid; otherwise NULL
	 *		   socketAddress[0]: IP address
	 *         socketAddress[1]: port number
	 *		   socketAddress[2]: unique connection ID 
	 */
	public String [] subscribeEvent (String domain, String name) {
		
		domain = domain.toLowerCase();
		name = name.toLowerCase();
		
		int i,j;		
		for (i=0;i<ContextAttribute.eventNames.length;i++)
		{
			if (name.equals(ContextAttribute.eventNames[i].toLowerCase()))
				break;
		}		
		if (i==ContextAttribute.eventNames.length)
			return null;			
		
		for (i=0;i<cd.length;i++)
			if (domain.equals(cd[i].name.toLowerCase()))
				break;
		if (i==cd.length)
		{
			System.err.println("Invalid name of context domain: " + domain);
			
			return null;
		}
		
		ContextAttribute ca;
		for (j=0;j<cd[i].contextAttrs.length;j++)
		{
			ca = cd[i].contextAttrs[j];
			if (ca.name.toLowerCase().equals(name))
				break;
		}		
		if (j==cd[i].contextAttrs.length)
		{
			System.err.println("Invalid name of context attribute " + name + " in domain " + domain);
		    	    
	    	return null;
		}
		
		if (!ContextDomain.checkEvent(domain,name))
		{
			System.err.println("Invalid name of event attribute " + name + " in domain " + domain);
		    	    
	    	return null;	
		}
			
		String socketAddress [] = new String [3];
		try {
			socketAddress[0] = 	ess.eventSocket.getInetAddress().getLocalHost().getHostAddress();		
			socketAddress[1] = String.valueOf(ess.eventSocket.getLocalPort());
			socketAddress[2] = String.valueOf(eventID);
			EventSubscription es = new EventSubscription();
			es.id = eventID;
			es.domainID = i;
			es.nameID = j;
			ess.subcribedEvents.addElement((EventSubscription)es);
			eventID ++;
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		
		return socketAddress;
		
	}  // subscribeEvent
	
	/**
	 * This function unsubscribes to a pre-subscribed event.
	 * @param connectionID unique connection ID between the caller and the PSG
	 * @return TRUE if the unsubscription is successfull; otherwise FALSE
	 */
	public boolean unsubscribeEvent (int connectionID) {
		
		int i;
		EventSubscription es;
		
		for (i=0;i<ess.subcribedEvents.size();i++) {
			es = (EventSubscription)ess.subcribedEvents.elementAt(i);
			if (es.id==connectionID) {
				es.ecs.isUnsubscribed = true;
				ess.subcribedEvents.removeElementAt(i);
				//psg.updateEventSubscriptions(es.ecs.eventID);
				return true;
			}
		}
		
		System.err.println("Invalid event subscription ID: " + eventID + " Cannot unsubcribe");
		
		return false;
		
	}  // unsubscribeEvent	
	
	/**
	 * This function subscribes to a specific event: value change of a context attribute.
	 * @param domain context domain that the context attribute belongs to
	 * @param name name of the attribute
	 * @return socket address of a Sever Socket that the caller can connects 
	 *         to later if the input attribute name if valid; otherwise NULL
	 *		   socketAddress[0]: IP address
	 *         socketAddress[1]: port number
	 *		   socketAddress[2]: unique connection ID 
	 */
	public String [] getContextChange (String domain, String name) {
		
		domain = domain.toLowerCase();
		name = name.toLowerCase();
		
		int i,j;
		for (i=0;i<cd.length;i++)
			if (domain.equals(cd[i].name.toLowerCase()))
				break;
		if (i==cd.length)
		{
			System.err.println("Invalid name of context domain: " + domain);
			
			return null;
		}
		
		ContextAttribute ca;
		for (j=0;j<cd[i].contextAttrs.length;j++)
		{
			ca = cd[i].contextAttrs[j];
			if (ca.name.toLowerCase().equals(name))
				break;
		}		
	    if (j==cd[i].contextAttrs.length)
	    	System.err.println("Invalid name of context attribute " + name + " in domain " + domain);
			
		String socketAddress [] = new String [3];
		try {
			socketAddress[0] = 	ess.eventSocket.getInetAddress().getLocalHost().getHostAddress();		
			socketAddress[1] = String.valueOf(ess.eventSocket.getLocalPort());
			socketAddress[2] = String.valueOf(eventID);
			EventSubscription es = new EventSubscription();
			es.id = eventID;
			es.domainID = i;
			es.nameID = j;
			ess.subcribedEvents.addElement((EventSubscription)es);
			eventID ++;
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		
		return socketAddress;
		
	}  // getContextChange
	
	/**
	 * This function unsubscribes to a specific event: value change of a context attribute.
	 * @param connectionID unique connection ID between the caller and the PSG
	 * @return TRUE if the unsubscription is successfull; otherwise FALSE
	 */
	public boolean stopGetContextChange (int connectionID) {
		
		return unsubscribeEvent(connectionID);
		
	}  // stopGetContextChange
	
	/**
	 * This function tells the client sockets corresponding to subscriptions of 
	 * a specific event that an ocurrence of the event is newly detected.
	 * @param eventID identifier of the event
	 */
	public void updateEventNotificationStatus(int eventID) {
		
		int i;
		EventSubscription es;
		for (i=0;i<ess.subcribedEvents.size();i++)
		{
			es = (EventSubscription)ess.subcribedEvents.elementAt(i);
			if (es.ecs.eventID == eventID)
				es.ecs.isNotified = false;
		}		
		
	}	// updateEventNotificationStatus
	
	/**
	 * This function examines whether an event occurrence has been notified to
	 * the client sockets corresponding to all subscriptions of the event.
	 * @param eventID identifier of the event
	 * @return TRUE if all callers are notified; otherwise FALSE
	 */
	public boolean isAllNotified(int eventID) {
		
		int i;
		EventSubscription es;
		
		for (i=0;i<ess.subcribedEvents.size();i++)
		{
			es = (EventSubscription)ess.subcribedEvents.elementAt(i);
			if (es.ecs.eventID == eventID)
				if (!es.ecs.isNotified)
					return false;
		}		
		
		return true;
		
	}	// isAllNotified
	
	/**
	 * This function prints all context attributes supported by the physical space and 
	 * their current values.
	 */
	public void printContextAttributes() {
		
		int i,j;
		ContextAttribute ca;
		for (i=0;i<cd.length;i++) 
		{   System.out.println("Context domain: " + cd[i].name);
			for (j=0;j<cd[i].contextAttrs.length;j++) {
				ca = (ContextAttribute)cd[i].contextAttrs[j];
				System.out.println(ca.name + " = " + ca.value);
			}
			System.out.println();
		}
		
	}  // printContextAttributes
}